package nancal.mp.service

import io.kubernetes.client.openapi.ApiClient
import io.kubernetes.client.openapi.ApiException
import io.kubernetes.client.openapi.apis.CoreV1Api
import io.kubernetes.client.openapi.apis.RbacAuthorizationV1Api
import io.kubernetes.client.openapi.models.*
import nancal.mp.util.setNamespace
import nancal.mp.util.tidyK8sYaml
import nbcp.base.comm.ApiResult
import nbcp.base.comm.JsonMap
import nbcp.base.comm.JsonResult
import nbcp.base.extend.*
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service

@Service
class K8sService : K8sBaseService() {
    companion object {
        private val logger = LoggerFactory.getLogger(this::class.java.declaringClass)
    }

    fun createNamespace(targetEnv: String, namespace: String): JsonResult {
        var api = CoreV1Api(getK8sClient(targetEnv));
        try {
            api.readNamespace(namespace).execute()
                .apply {
                    if (this.status != null) {
                        return JsonResult();
                    }
                }
        } catch (e: Exception) {

        }


        var ns = V1Namespace()
        ns.metadata = V1ObjectMeta()
        ns.metadata!!.name = namespace;
        var n = api.createNamespace(ns).execute()
        if (n == null) {
            return JsonResult.error("创建失败")
        }
        return JsonResult();
    }

    fun getServiceType(targetEnv: String, namespace: String, name: String): String {
        return getServiceType(getK8sClient(targetEnv), namespace, name)
    }

    /**
     * @return 可能值:  ClusterIP , NodePort ,ExternalName
     */
    fun getServiceType(client: ApiClient, namespace: String, name: String): String {
        var api = CoreV1Api(client);

        var ret = try {
            api.readNamespacedService(name, namespace).execute()
        } catch (ex: ApiException) {
            if (ex.code == 404) {
                return "";
            }

            throw ex;
        }

        if (ret.status == null) {
            return "";
        }

        return ret.spec?.type ?: ""
    }

    fun applyServiceYaml(
        targetEnv: String,
        namespace: String,
        serviceName: String,
        serviceYaml: String
    ): JsonResult {
        return applyServiceYaml(getK8sClient(targetEnv), namespace, serviceName, serviceYaml)
    }

    fun applyServiceYaml(
        client: ApiClient,
        namespace: String,
        serviceName: String,
        serviceYaml: String
    ): JsonResult {

        var api = CoreV1Api(client);
        var content = serviceYaml;

        if (namespace.HasValue) {
            var map = serviceYaml.FromYamlText<JsonMap>()
            map.setNamespace(namespace)

            content = map.ToYaml();
        }

        var v1Service = try {
            V1Service.fromJson(content.FromYamlText(JsonMap::class.java).ToJson())

        } catch (e: Exception) {
            logger.error(content);
            throw e;
        }

        if (getServiceType(client, namespace, serviceName).isEmpty()) {

            var s: V1Service? = null;
            try {
                s = api.createNamespacedService(
                    namespace.AsString("default"), v1Service
                ).execute()
            } catch (e: Exception) {
                logger.error(content)
                if (s != null) {
                    logger.error("service status:" + s!!.status.toString())
                }
                throw e;
            }

            if (s == null) {
                return JsonResult.error("创建失败")
            }
        } else {
            logger.Important("跳过已存在的 Service, ${namespace}.${serviceName}")
            //replaceNamespacedService 会报错
//            api.replaceNamespacedService(dockerName, namespace, v1Service, null, null, null);
        }

        return JsonResult()
    }

    fun applyClusterRoleBinding(
        targetEnv: String,
        serviceName: String,
        serviceYaml: String
    ): JsonResult {

        var api = RbacAuthorizationV1Api(getK8sClient(targetEnv));

        var v1ClusterRoleBinding = try {
            V1ClusterRoleBinding.fromJson(serviceYaml.FromYamlText(JsonMap::class.java).ToJson())
        } catch (e: Exception) {
            logger.error(serviceYaml);
            throw e;
        }

        var exists = try {
            api.readClusterRoleBinding(serviceName).execute()

                .run {
                    if (this == null) return@run false;
                    if (this.kind.isNullOrEmpty()) return@run false;
                    return@run true;
                }
        } catch (ex: Exception) {
            false;
        }

        if (exists) {
            logger.Important("跳过部署已存在的${serviceName}")
            return JsonResult()
        }

        var b = api.createClusterRoleBinding(
            v1ClusterRoleBinding
        ).execute()

        if (b == null) {
            return JsonResult.error("创建失败")
        }
        return JsonResult()
    }

    fun deleteEndpointWithService(
        targetEnv: String,
        namespace: String,
        name: String
    ): JsonResult {
        if (namespace == "default") {
            return JsonResult.error("不能删除 default 名称的endpoint!")
        }

        var api = CoreV1Api(getK8sClient(targetEnv));

        var list2 = api.listNamespacedEndpoints(
            namespace
        )
            .fieldSelector("metadata.name=${name}")
            .limit(999)
            .execute()

        if (list2.items.any() == false) {
            return JsonResult();
        }

        var result = api.deleteNamespacedEndpoints(
            name,
            namespace
        )
            .execute();

        if (result.code != 200) {
            return JsonResult.error("删除已存在的 endpoint:${name} 失败")
        }


        var r2 = api.deleteNamespacedService(
            name, namespace
        )
            .execute()

        if (r2 == null) {
            return JsonResult.error("删除已存在的 endpoint:${name} 失败")
        }

        return JsonResult();
    }

    fun createEndpointWithService(
        targetEnv: String,
        namespace: String,
        name: String,
        fromNamespace: String,
        port: Int
    ): JsonResult {
        if (namespace == "default") {
            return JsonResult.error("不能删除 default 名称的endpoint!")
        }

        var api = CoreV1Api(getK8sClient(targetEnv));

        var list = api.listNamespacedEndpoints(
            fromNamespace
        )
            .fieldSelector("metadata.name=${name}")
            .limit(999)
            .execute()

        if (list.items.any() == false) {
            return JsonResult.error("default 名称空间下找不到 endpoint: ${name}")
        }


        var list2 = api.listNamespacedEndpoints(
            namespace
        )
            .fieldSelector("metadata.name=${name}")
            .limit(999)
            .execute()

        if (list2.items.any()) {
            var options = V1DeleteOptions();
            var result = api.deleteNamespacedEndpoints(
                name,
                namespace
            ).execute()

            if (result.code != 200) {
                return JsonResult.error("删除已存在的 endpoint:${name} 失败")
            }
        }

        var targetIp = list.items.first().subsets!!.first().addresses!!.first().ip;
        var targetPort = list.items.first().subsets!!.first().ports!!.first().port;

        var endPointYaml = """
apiVersion: "v1"
kind: "Endpoints"
metadata:
  name: "${name}"
  namespace: "${namespace}"
subsets:
- addresses:
  - ip: "${targetIp}"
  ports:
  - port: ${targetPort}
    protocol: "TCP"
"""

        var v1EndPoint = try {
            V1Endpoints.fromJson(endPointYaml.FromYamlText(JsonMap::class.java).ToJson())
        } catch (e: Exception) {
            logger.error(endPointYaml);
            throw e;
        }

        var ep = api.createNamespacedEndpoints(namespace, v1EndPoint).execute()

        if (ep == null) {
            return JsonResult.error("创建失败")
        }

        var serviceYaml = """
apiVersion: "v1"
kind: "Service"
metadata:
  name: "${name}"
  namespace: "${namespace}"
spec:
  ipFamilies:
  - "IPv4"
  ipFamilyPolicy: "SingleStack"
  ports:
  - port: ${port}
    protocol: "TCP"
    targetPort: ${targetPort}
  sessionAffinity: "None"
  type: "ClusterIP"
"""
        applyServiceYaml(targetEnv, namespace, name, serviceYaml)

        return JsonResult()
    }


    fun createExternalService(
        targetEnv: String,
        namespace: String,
        name: String,
        sourceNamespace: String,
        sourceName: String
    ): JsonResult {

        assert(namespace.HasValue) { "名称空间不能为空" }
        assert(name.HasValue) { "名称不能为空" }
        assert(sourceNamespace.HasValue) { "源名称空间不能为空" }

        var v1Service = """
---
apiVersion: "v1"
kind: "Service"
metadata:
  name: ${name}
  namespace: ${namespace}
spec:
  externalName: "${sourceName}.${sourceNamespace}.svc.cluster.local"
  type: "ExternalName"
"""

        return applyServiceYaml(targetEnv, namespace, name, v1Service);
    }

    fun getServiceYaml(targetEnv: String, namespace: String, name: String): String {
        return getServiceYaml(getK8sClient(targetEnv), namespace, name);
    }

    fun getServiceYaml(client: ApiClient, namespace: String, name: String): String {
        var api = CoreV1Api(client);

        var ret = try {
            api.readNamespacedService(name, namespace.AsString("default")).execute()
        } catch (ex: ApiException) {
            if (ex.code == 404) {
                return "";
            }

            throw ex;
        }

        if (ret.status == null) {
            return "";
        }

        var map = ret.ConvertType(JsonMap::class.java) as JsonMap?
        if (map == null) {
            return ""
        }

        map.tidyK8sYaml();

        return map.ToYaml()
    }

    fun existsService(targetEnv: String, namespace: String, name: String): ApiResult<Boolean> {
        return ApiResult.of(getServiceType(targetEnv, namespace, name).HasValue)
    }

    fun deleteService(targetEnv: String, targetNamespace: String, name: String): ApiResult<Boolean> {
        var api = CoreV1Api(getK8sClient(targetEnv));
        var options = V1DeleteOptions();

        api.deleteNamespacedService(name, targetNamespace).execute()
        return ApiResult();
    }
}